/// <reference path="../model/Point.ts" />
/// <reference path="../model/BlockPoint.ts" />

namespace tetris {
    import Point = model.Point;
    import BlockPoint = model.BlockPoint;

    export type MatrixData = Array<Array<BlockPoint | null>>;

    export class Matrix {
        static createRow(width) {
            return Array.apply(null, { length: width });
        }

        static createMatrix(width, height) {
            return Array.apply(null, { length: height }).map(() => {
                return this.createRow(width);
            });
        }

        private _width: number;
        private _height: number;
        private _data: MatrixData;

        constructor(width: number, height: number) {
            this._width = width;
            this._height = height;
            this.reset();
        }

        get width(): number {
            return this._width;
        }

        get height(): number {
            return this._height;
        }

        addBlockPoints(points: Array<BlockPoint>) {
            points.forEach(point => {
                this.set(point);
            });
        }

        set(point: BlockPoint);
        set(x: number, y: number, point?: BlockPoint);
        set(first: number | BlockPoint, y?: number, point: BlockPoint = null) {
            if (typeof first === "number") {
                this._data[y][first] = point;
            } else {
                this._data[first.y][first.x] = first;
            }
        }

        getFullRows(): Array<number> {
            const fulls = [];
            this._data.forEach((row, i) => {
                if (row.length && row.every(t => !!t)) {
                    fulls.push(i);
                }
            });
            return fulls;
        }

        removeRows(rows: Array<number>) {
            if (!rows.length) {
                return;
            }

            const newData = Matrix.createMatrix(this.width, rows.length);
            let next = 0;
            this._data.forEach((row, i) => {
                if (i === rows[next]) {
                    next++;
                    if (next >= rows.length) {
                        next = -1;
                    }
                } else {
                    row.filter(t => t).forEach(t => t.set(void 0, newData.length));
                    newData.push(row);
                }
            });

            this._data = newData;
        }

        reset() {
            this._data = Matrix.createMatrix(this.width, this.height);
        }

        fasten() {
            return this._data.reduce((all, row) => {
                return all.concat(row.filter(t => t));
            }, []);
        }

        isPutable(points: Array<Point>) {
            const [width, height] = [this.width, this.height];

            return points.every(t => {
                if (t.x < 0 || t.x >= width || t.y < 0 || t.y >= height) {
                    return false;
                }

                return !this._data[t.y][t.x];
            });
        }
    }
}
